﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using SlimDX;
using SlimDX.DXGI;
using SlimDX.Direct3D11;

using Device = SlimDX.Direct3D11.Device;

namespace LibSnesDotNet.Video
{
	class Direct3D11Base
	{
		bool _isInitializing;

		Form _form;
		bool _isComposited;

		Factory _factory;
		SwapChain _swapChain;
		Device _device;
		bool _isFullscreen;
		bool _isResizing;
		bool _hasSizeChanged;

		public event EventHandler FullscreenToggled;
		public event EventHandler BuffersChanging;
		public event EventHandler BuffersChanged;

		public Direct3D11Base(Form form,
		                         SwapChainDescription swapDesc,
		                         Adapter adapter = null,
		                         DriverType type = DriverType.Hardware,
		                         DeviceCreationFlags flags = DeviceCreationFlags.None,
		                         FeatureLevel[] levels = null)
		{
			IsDisposed = false;

			try
			{
				_isInitializing = true;

				_form = form;

				_isComposited = DwmApi.IsCompositionEnabled;
				if (_isComposited)
				{
					DwmApi.EnableMMCSS(true);
					DwmPresentParameters present = new DwmPresentParameters()
					{
						IsQueued = true,
						BufferCount = 2,
						RefreshesPerFrame = 1,
					};
					DwmApi.SetPresentParameters(form.Handle, ref present);
				}

				if (swapDesc.OutputHandle != IntPtr.Zero) { throw new ArgumentException("Output handle must not be set."); }
				if (swapDesc.Usage != Usage.RenderTargetOutput) { throw new ArgumentException("Usage must be RenderTargetOutput."); }

				swapDesc.OutputHandle = _form.Handle;
				bool setFullscreen = !swapDesc.IsWindowed;
				swapDesc.IsWindowed = true;

				Device.CreateWithSwapChain(adapter, type, DeviceCreationFlags.None, levels, swapDesc, out _device, out _swapChain);
				_swapChain.ResizeTarget(swapDesc.ModeDescription);
				_factory = _swapChain.GetParent<Factory>();
				_factory.SetWindowAssociation(_form.Handle, WindowAssociationFlags.IgnoreAll);

				_form.SizeChanged += SizeChanged_Handler;
				_form.ResizeBegin += ResizeBegin_Handler;
				_form.Resize += Resize_Handler;
				_form.ResizeEnd += ResizeEnd_Handler;
				_form.KeyDown += KeyDown_Handler;

				if (setFullscreen)
				{
					ChangeMode(true);
				}

				_isInitializing = false;
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		public Form Form { get { return _form; } }
		public bool IsComposited { get { return _isComposited; } }
		public Factory DxgiFactory { get { return _factory; } }
		public Device D3D11Device { get { return _device; } }
		public bool IsFullscreen { get { return _isFullscreen; } }
		public SwapChainDescription SwapChainDescription
		{
			get
			{
				AssertUndisposed();

				return _swapChain.Description;
			}
		}
		public FrameStatistics FrameStatistics
		{
			get
			{
				AssertUndisposed();
				if (!_isFullscreen) { throw new InvalidOperationException("Frame stats only available while fullscreen."); }

				return _swapChain.FrameStatistics;
			}
		}
		protected bool IsInitializing { get { return _isInitializing; } }

		public void ChangeMode(bool setFullscreen, ModeDescription mode)
		{
			AssertUndisposed();

			Output output;
			_swapChain.GetFullScreenState(out _isFullscreen, out output);

			if (setFullscreen && !_isFullscreen)
			{
				_swapChain.ResizeTarget(mode);
				_swapChain.SetFullScreenState(true, null);
			}
			else if (!setFullscreen && _isFullscreen)
			{
				_swapChain.SetFullScreenState(false, null);
				_swapChain.ResizeTarget(mode);
			}
			else
			{
				_swapChain.ResizeTarget(mode);
			}

			_swapChain.GetFullScreenState(out _isFullscreen, out output);
		}

		public void ChangeMode(bool setFullscreen)
		{
			AssertUndisposed();

			_form.Focus();
			_swapChain.SetFullScreenState(setFullscreen, null);

			Output output;
			_swapChain.GetFullScreenState(out _isFullscreen, out output);
		}

		/*public void ChangeBuffers(int count, Format format, SwapChainFlags flags)
		{
			AssertUndisposed();

			ModeDescription modeDesc = _swapChain.Description.ModeDescription;
			_swapChain.ResizeBuffers(count, modeDesc.Width, modeDesc.Height, modeDesc.Format, flags);
		}*/

		public RenderTargetView GetBackBufferView(int buffer = 0)
		{
			AssertUndisposed();

			return new RenderTargetView(_device, Texture2D.FromSwapChain<Texture2D>(_swapChain, buffer));
		}

		public Result Present(int syncInterval, PresentFlags flags)
		{
			AssertUndisposed();

			return _swapChain.Present(syncInterval, flags);
		}

		protected virtual void OnFullscreenToggled()
		{
			if (FullscreenToggled != null) { FullscreenToggled(this, new EventArgs()); }
		}

		protected virtual void OnBuffersChanging()
		{
			if (BuffersChanging != null) { BuffersChanging(this, new EventArgs()); }
		}

		protected virtual void OnBuffersChanged()
		{
			if (BuffersChanged != null) { BuffersChanged(this, new EventArgs()); }
		}

		void ResizeBegin_Handler(object sender, EventArgs e)
		{
			_isResizing = true;
		}

		void Resize_Handler(object sender, EventArgs e)
		{
			_hasSizeChanged = true;
		}

		void ResizeEnd_Handler(object sender, EventArgs e)
		{
			_isResizing = false;

			if (_hasSizeChanged) { SizeChanged_Handler(sender, e); }

			_hasSizeChanged = false;
		}

		void SizeChanged_Handler(object sender, EventArgs e)
		{
			if (_isResizing) { return; }

			var newSize = _form.ClientSize;

			if (newSize.Width != 0 && newSize.Height != 0)
			{
				if (!_isInitializing) { OnBuffersChanging(); }

				SwapChainDescription desc = _swapChain.Description;
				_swapChain.ResizeBuffers(desc.BufferCount, newSize.Width, newSize.Height, desc.ModeDescription.Format, desc.Flags);

				if (!_isInitializing) { OnBuffersChanged(); }
			}
		}

		void KeyDown_Handler(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Enter && e.Modifiers == Keys.Alt)
			{
				if (FullscreenToggled != null) { FullscreenToggled(this, new EventArgs()); }
				e.SuppressKeyPress = true;
			}
			if (e.KeyCode == Keys.Menu)
			{
				e.Handled = true;
			}
		}

		public bool IsDisposed { get; private set; }

		protected void Dispose()
		{
			AssertUndisposed();
			IsDisposed = true;

			_form.SizeChanged -= SizeChanged_Handler;
			_form.KeyDown -= KeyDown_Handler;

			if (_swapChain != null)
			{
				_swapChain.SetFullScreenState(false, null);
				_swapChain.Dispose();
			}
			if (_device != null) { _device.Dispose(); }
			if (_factory != null) { _factory.Dispose(); }
		}

		public void AssertUndisposed()
		{
			if (IsDisposed) { throw new ObjectDisposedException(ToString()); }
		}
	}
}
